{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Working on a Windows 10\n",
"Python version 3.11.8 | packaged by conda-forge | (main, Feb 16 2024, 20:40:50) [MSC v.1937 64 bit (AMD64)]\n",
"Pandas version 2.2.3\n",
"bifacial_radiance version 0.5.0b2.dev4+gedb973d.d20250924\n"
]
}
],
"source": [
"# This information helps with debugging and getting support :)\n",
"import sys, platform\n",
"import pandas as pd\n",
"import bifacial_radiance as br\n",
"print(\"Working on a \", platform.system(), platform.release())\n",
"print(\"Python version \", sys.version)\n",
"print(\"Pandas version \", pd.__version__)\n",
"print(\"bifacial_radiance version \", br.__version__)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 8 - Electrical Mismatch Method (sorry, this tutorial is deprecated and non-functional as of v0.5.0)\n",
"\n",
"Nonuniform rear-irradiance on bifacial PV systems can cause additional mismatch loss, which may not be appropriately captured in PV energy production estimates and software.\n",
"\n",
"\n",
"\n",
"The **analysis.py** module in bifacial_radiance comes with functions to calculate power output, electrical mismatch, and some other irradiance calculations. This is the procedure used for this proceedings and submitted journals, which have much more detail on the procedure. \n",
"\n",
"* Deline, C., Ayala Pelaez, S., MacAlpine, S., Olalla, C. Estimating and Parameterizing Mismatch Power Loss in Bifacial Photovoltaic Systems. Progress in PV 2020, https://doi.org/10.1002/pip.3259\n",
"\n",
"* Deline C, Ayala Pelaez S, MacAlpine S, Olalla C. Bifacial PV System Mismatch Loss Estimation & Parameterization. Presented in: 36th EU PVSEC, Marseille Fr. Slides: https://www.nlr.gov/docs/fy19osti/74885.pdf. Proceedings: https://www.nlr.gov/docs/fy20osti/73541.pdf\n",
"\n",
"* Ayala Pelaez S, Deline C, MacAlpine S, Olalla C. Bifacial PV system mismatch loss estimation. Poster presented at the 6th BifiPV Workshop, Amsterdam 2019. https://www.nlr.gov/docs/fy19osti/74831.pdf and http://bifipv-workshop.com/index.php?id=amsterdam-2019-program \n",
"\n",
"Ideally **mismatch losses M** should be calculated for the whole year, and then the **mismatch loss factor to apply to Grear \"Lrear\"** required by due diligence softwares can be calculated:\n",
"\n",
"
\n",
"\n",
"In this journal we will explore calculating mismatch loss M for a reduced set of hours. A procedure similar to that in Tutorial 3 will be used to generate various hourly irradiance measurements in the results folder, which the mismatch.py module will load and analyze. Analysis is done with PVMismatch, so this must be installed.\n",
"\n",
"## STEPS:\n",
" 1. Run an hourly simulation\n",
" 2. Do mismatch analysis on the results.\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 1. Run an hourly simulation\n",
"\n",
"This will generate the results over which we will perform the mismatch analysis. Here we are doing only 1 day to make this faster."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"path = F:\\Documents\\Python Scripts\\bifacial_radiance\\bifacial_radiance\\TEMP\\Tutorial_08\n",
"Loading albedo, 1 value(s), 0.250 avg\n",
"1 nonzero albedo values.\n",
"Getting weather file: USA_VA_Richmond.724010_TMY2.epw\n",
" ... OK!\n",
"8760 line in WeatherFile. Assuming this is a standard hourly WeatherFile for the year for purposes of saving Gencumulativesky temporary weather files in EPW folder.\n",
"Coercing year to 2021\n",
"Filtering dates\n",
"Saving file EPWs\\metdata_temp.csv, # points: 8760\n",
"Calculating Sun position for Metdata that is right-labeled with a delta of -30 mins. i.e. 12 is 11:30 sunpos\n",
"\n",
"Module Name: PVmodule\n",
"Module PVmodule updated in module.json\n",
"Pre-existing .rad file objects\\PVmodule.rad will be overwritten\n",
"\n",
"Creating ~3 skyfiles. \n",
"Created 3 skyfiles in /skies/\n",
"\n",
"Making ~3 .rad files for gendaylit 1-axis workflow (this takes a minute..)\n",
"3 Radfiles created in /objects/\n",
"\n",
"Making 3 octfiles in root directory.\n",
"Created 1axis_2021-11-06_0800.oct\n",
"Created 1axis_2021-11-06_0900.oct\n",
"Created 1axis_2021-11-06_1000.oct\n",
"Linescan in process: 1axis_2021-11-06_0800_Scene0_Row4_Module10_Front\n",
"Linescan in process: 1axis_2021-11-06_0800_Scene0_Row4_Module10_Back\n",
"Saved: results\\irr_1axis_2021-11-06_0800_Scene0_Row4_Module10.csv\n",
"Index: 2021-11-06_0800. Wm2Front: 216.943925. Wm2Back: 6.079492166666667\n",
"Linescan in process: 1axis_2021-11-06_0900_Scene0_Row4_Module10_Front\n",
"Linescan in process: 1axis_2021-11-06_0900_Scene0_Row4_Module10_Back\n",
"Saved: results\\irr_1axis_2021-11-06_0900_Scene0_Row4_Module10.csv\n",
"Index: 2021-11-06_0900. Wm2Front: 371.8801583333333. Wm2Back: 34.6894225\n",
"Linescan in process: 1axis_2021-11-06_1000_Scene0_Row4_Module10_Front\n",
"Linescan in process: 1axis_2021-11-06_1000_Scene0_Row4_Module10_Back\n",
"Saved: results\\irr_1axis_2021-11-06_1000_Scene0_Row4_Module10.csv\n",
"Index: 2021-11-06_1000. Wm2Front: 335.72138333333334. Wm2Back: 41.0856625\n"
]
}
],
"source": [
"import bifacial_radiance\n",
"import os \n",
"from pathlib import Path\n",
"\n",
"testfolder = str(Path().resolve().parent.parent / 'bifacial_radiance' / 'TEMP'/ 'Tutorial_08')\n",
"if not os.path.exists(testfolder):\n",
" os.makedirs(testfolder)\n",
"\n",
"simulationName = 'tutorial_8'\n",
"moduletype = \"PVmodule\"\n",
"albedo = 0.25 \n",
"lat = 37.5 \n",
"lon = -77.6\n",
"\n",
"# Scene variables\n",
"nMods = 20\n",
"nRows = 7\n",
"hub_height = 1.5 # meters\n",
"gcr = 0.33\n",
"\n",
"# Traking parameters\n",
"cumulativesky = False\n",
"limit_angle = 60 \n",
"angledelta = 0.01 \n",
"backtrack = True \n",
"\n",
"#makeModule parameters\n",
"x = 1\n",
"y = 2\n",
"xgap = 0.01\n",
"zgap = 0.05\n",
"ygap = 0.0 # numpanels=1 anyways so it doesnt matter anyway\n",
"numpanels = 1\n",
"axisofrotationTorqueTube = True\n",
"diameter = 0.1\n",
"tubetype = 'Oct' \n",
"material = 'black'\n",
"tubeParams = {'diameter':diameter,\n",
" 'tubetype':tubetype,\n",
" 'material':material,\n",
" 'axisofrotation':axisofrotationTorqueTube,\n",
" 'visible':True}\n",
"\n",
"# Analysis parmaeters\n",
"startdate = '11_06_08' # Options: mm_dd, mm_dd_HH, mm_dd_HHMM, YYYY-mm-dd_HHMM\n",
"enddate = '11_06_10'\n",
"sensorsy = 12\n",
"\n",
"demo = bifacial_radiance.RadianceObj(simulationName, path=testfolder) \n",
"demo.setGround(albedo) \n",
"epwfile = demo.getEPW(lat,lon) \n",
"metdata = demo.readWeatherFile(epwfile, starttime=startdate, endtime=enddate) \n",
"mymodule = demo.makeModule(name=moduletype, x=x, y=y, xgap=xgap,\n",
" ygap = ygap, zgap=zgap, numpanels=numpanels, tubeParams=tubeParams)\n",
"pitch = mymodule.sceney/gcr\n",
"sceneDict = {'pitch':pitch,'hub_height':hub_height, 'nMods': nMods, 'nRows': nRows} \n",
"demo.set1axis(limit_angle = limit_angle, backtrack = backtrack, gcr = gcr, cumulativesky = cumulativesky)\n",
"demo.gendaylit1axis()\n",
"demo.makeScene1axis(module=mymodule, sceneDict=sceneDict) \n",
"demo.makeOct1axis()\n",
"demo.analysis1axis(sensorsy = sensorsy);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 2. Do mismatch analysis on the results\n",
"\n",
"There are various things that we need to know about the module at this stage.\n",
"\n",
"* Orientation: If it was simulated in portrait or landscape orientation. \n",
"* Number of cells in the module: options right now are 72 or 96\n",
"* Bifaciality factor: this is how well the rear of the module performs compared to the front of the module, and is a spec usually found in the datasheet. \n",
"\n",
"Also, if the number of sampling points (`sensorsy`) from the result files does not match the number of cells along the panel orientation, downsampling or upsamplinb will be peformed. For this example, the module is in portrait mode (y > x), so there will be 12 cells along the collector width (**numcellsy**), and that's why we set **sensorsy = 12** during the analysis above. \n",
"\n",
"These are the re-sampling options. To downsample, we suggest sensorsy >> numcellsy (for example, we've tested sensorsy = 100,120 and 200)\n",
" - Downsamping by Center - Find the center points of all the sensors passed \n",
" - Downsampling by Average - averages irradiances that fall on what would consist on the cell\n",
" - Upsample\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"6 files in the directory\n"
]
}
],
"source": [
"resultfolder = os.path.join(testfolder, 'results')\n",
"writefiletitle = \"Mismatch_Results.csv\" \n",
"\n",
"portraitorlandscape='portrait' # Options are 'portrait' or 'landscape'\n",
"bififactor=0.9 # Bifaciality factor DOES matter now, as the rear irradiance values will be multiplied by this factor.\n",
"numcells= 72# Options are 72 or 96 at the moment.\n",
"downsamplingmethod = 'byCenter' # Options are 'byCenter' or 'byAverage'.\n",
"analysisIrradianceandPowerMismatch(testfolder=resultfolder, writefiletitle=writefiletitle, portraitorlandscape=portraitorlandscape, \n",
" bififactor=bififactor, numcells=numcells)\n",
"\n",
"print (\"Your hourly mismatch values are now saved in the file above! :D\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" Reads and calculates power output and mismatch for each file in the \n",
"testfolder where all the bifacial_radiance irradiance results .csv are saved.\n",
"First load each file, cleans it and resamples it to the numsensors set in this function,\n",
"and then calculates irradiance mismatch and PVMismatch power output for averaged, minimum,\n",
"or detailed irradiances on each cell for the cases of A) only 12 or 8 downsmaples values are\n",
"considered (at the center of each cell), and B) 12 or 8 values are obtained from averaging\n",
"all the irradiances falling in the area of the cell (No edges or inter-cell spacing are considered\n",
"at this moment). Then it saves all the A and B irradiances, as well as the cleaned/resampled\n",
"front and rear irradiances.\n",
"\n",
"Ideally sensorsy in the read data is >> 12 to give results for the irradiance mismatch in the cell."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"def analysisIrradianceandPowerMismatch(testfolder, writefiletitle, portraitorlandscape, bififactor, numcells=72, downsamplingmethod='byCenter'):\n",
" r'''\n",
" Use this when sensorsy calculated with bifacial_radiance > cellsy\n",
" \n",
" \n",
" Parameters\n",
" ----------\n",
" testfolder: folder containing output .csv files for bifacial_radiance\n",
" writefiletitle: .csv title where the output results will be saved. \n",
" portraitorlandscape: 'portrait' or 'landscape', for PVMismatch input\n",
" which defines the electrical interconnects inside the module. \n",
" bififactor: bifaciality factor of the module. Max 1.0. ALL Rear irradiance values saved include the bifi-factor.\n",
" downsampling method: 1 - 'byCenter' - 2 - 'byAverage'\n",
" \n",
" Example:\n",
" \n",
" # User information.\n",
" import bifacial_radiance\n",
" testfolder=r'C:\\Users\\sayala\\Documents\\HPC_Scratch\\EUPVSEC\\HPC Tracking Results\\RICHMOND\\Bifacial_Radiance Results\\PVPMC_0\\results'\n",
" writefiletitle= r'C:\\Users\\sayala\\Documents\\HPC_Scratch\\EUPVSEC\\HPC Tracking Results\\RICHMOND\\Bifacial_Radiance Results\\PVPMC_0\\test_df.csv'\n",
" sensorsy=100\n",
" portraitorlandscape = 'portrait'\n",
" analysis.analysisIrradianceandPowerMismatch(testfolder, writefiletitle, portraitorlandscape, bififactor=1.0, numcells=72)\n",
"\n",
" '''\n",
" from bifacial_radiance import load\n",
" import os, glob\n",
" import pandas as pd\n",
" \n",
" # Default variables \n",
" numpanels=1 # 1 at the moment, necessary for the cleaning routine.\n",
" automatic=True\n",
" \n",
" #loadandclean\n",
" # testfolder = r'C:\\Users\\sayala\\Documents\\HPC_Scratch\\EUPVSEC\\PinPV_Bifacial_Radiance_Runs\\HPCResults\\df4_FixedTilt\\FixedTilt_Cairo_C_0.15\\results'\n",
" filelist = sorted(os.listdir(testfolder)) \n",
" #filelist = sorted(glob.glob(os.path.join('testfolder','*.csv'))) \n",
" print('{} files in the directory'.format(filelist.__len__()))\n",
"\n",
" # Check number of sensors on data.\n",
" temp = load.read1Result(os.path.join(testfolder,filelist[0]))\n",
" sensorsy = len(temp)\n",
"\n",
" # Setup PVMismatch parameters\n",
" stdpl, cellsx, cellsy = _setupforPVMismatch(portraitorlandscape=portraitorlandscape, sensorsy=sensorsy, numcells=numcells)\n",
"\n",
" F=pd.DataFrame()\n",
" B=pd.DataFrame()\n",
" for z in range(0, filelist.__len__()):\n",
" data=load.read1Result(os.path.join(testfolder,filelist[z]))\n",
" [frontres, backres] = load.deepcleanResult(data, sensorsy=sensorsy, numpanels=numpanels, automatic=automatic)\n",
" F[filelist[z]]=frontres\n",
" B[filelist[z]]=backres \n",
"\n",
" B = B*bififactor\n",
" # Downsample routines:\n",
" if sensorsy > cellsy:\n",
" if downsamplingmethod == 'byCenter':\n",
" print(\"Sensors y > cellsy; Downsampling data by finding CellCenter method\")\n",
" F = _sensorsdownsampletocellbyCenter(F, cellsy)\n",
" B = _sensorsdownsampletocellbyCenter(B, cellsy)\n",
" elif downsamplingmethod == 'byAverage':\n",
" print(\"Sensors y > cellsy; Downsampling data by Averaging data into Cells method\")\n",
" F = _sensorsdownsampletocellsbyAverage(F, cellsy)\n",
" B = _sensorsdownsampletocellsbyAverage(B, cellsy)\n",
" else:\n",
" print (\"Sensors y > cellsy for your module. Select a proper downsampling method ('byCenter', or 'byAverage')\")\n",
" return\n",
" elif sensorsy < cellsy:\n",
" print(\"Sensors y < cellsy; Upsampling data by Interpolation\")\n",
" F = _sensorupsampletocellsbyInterpolation(F, cellsy)\n",
" B = _sensorupsampletocellsbyInterpolation(B, cellsy)\n",
" elif sensorsy == cellsy:\n",
" print (\"Same number of sensorsy and cellsy for your module.\")\n",
" F = F\n",
" B = B\n",
"\n",
" # Calculate POATs\n",
" Poat = F+B\n",
" \n",
" # Define arrays to fill in:\n",
" Pavg_all=[]; Pdet_all=[]\n",
" Pavg_front_all=[]; Pdet_front_all=[]\n",
" colkeys = F.keys()\n",
" \n",
" import pvmismatch\n",
" \n",
" if cellsx*cellsy == 72:\n",
" cell_pos = pvmismatch.pvmismatch_lib.pvmodule.STD72\n",
" elif cellsx*cellsy == 96:\n",
" cell_pos = pvmismatch.pvmismatch_lib.pvmodule.STD96\n",
" else:\n",
" print(\"Error. Only 72 and 96 cells modules supported at the moment. Change numcells to either of this options!\")\n",
" return\n",
" \n",
" pvmod=pvmismatch.pvmismatch_lib.pvmodule.PVmodule(cell_pos=cell_pos) \n",
" pvsys = pvmismatch.pvsystem.PVsystem(numberStrs=1, numberMods=1, pvmods=pvmod) \n",
"\n",
"\n",
" # Calculate powers for each hour:\n",
" for i in range(0,len(colkeys)): \n",
" Pavg, Pdet = calculatePVMismatch(pvsys = pvsys, stdpl=stdpl, cellsx=cellsx, cellsy=cellsy, Gpoat=list(Poat[colkeys[i]]/1000))\n",
" Pavg_front, Pdet_front = calculatePVMismatch(pvsys = pvsys, stdpl = stdpl, cellsx = cellsx, cellsy = cellsy, Gpoat= list(F[colkeys[i]]/1000))\n",
" Pavg_all.append(Pavg)\n",
" Pdet_all.append(Pdet)\n",
" Pavg_front_all.append(Pavg_front) \n",
" Pdet_front_all.append(Pdet_front)\n",
" \n",
" ## Rename Rows and save dataframe and outputs.\n",
" F.index='FrontIrradiance_cell_'+F.index.astype(str)\n",
" B.index='BackIrradiance_cell_'+B.index.astype(str)\n",
" Poat.index='POAT_Irradiance_cell_'+Poat.index.astype(str)\n",
"\n",
" # Statistics Calculatoins\n",
" dfst=pd.DataFrame()\n",
" dfst['MAD/G_Total'] = mad_fn(Poat)\n",
" dfst['Front_MAD/G_Total'] = mad_fn(F)\n",
" dfst['MAD/G_Total**2'] = dfst['MAD/G_Total']**2\n",
" dfst['Front_MAD/G_Total**2'] = dfst['Front_MAD/G_Total']**2\n",
" dfst['poat'] = Poat.mean()\n",
" dfst['gfront'] = F.mean()\n",
" dfst['grear'] = B.mean()\n",
" dfst['bifi_ratio'] = dfst['grear']/dfst['gfront']\n",
" dfst['stdev'] = Poat.std()/ dfst['poat']\n",
" dfst.index=Poat.columns.astype(str)\n",
"\n",
" # Power Calculations/Saving\n",
" Pout=pd.DataFrame()\n",
" Pout['Pavg']=Pavg_all\n",
" Pout['Pdet']=Pdet_all\n",
" Pout['Front_Pavg']=Pavg_front_all\n",
" Pout['Front_Pdet']=Pdet_front_all\n",
" Pout['Mismatch_rel'] = 100-(Pout['Pdet']*100/Pout['Pavg'])\n",
" Pout['Front_Mismatch_rel'] = 100-(Pout['Front_Pdet']*100/Pout['Front_Pavg']) \n",
" Pout.index=Poat.columns.astype(str)\n",
"\n",
" ## Save CSV as one long row\n",
" df_all = pd.concat([Pout, dfst, Poat.T, F.T, B.T], axis=1)\n",
" df_all.to_csv(writefiletitle)\n",
" print(\"Saved Results to \", writefiletitle)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"def _setupforPVMismatch(portraitorlandscape, sensorsy, numcells=72):\n",
" r''' Sets values for calling PVMismatch, for ladscape or portrait modes and \n",
" \n",
" Example:\n",
" stdpl, cellsx, cellsy = _setupforPVMismatch(portraitorlandscape='portrait', sensorsy=100):\n",
" '''\n",
"\n",
" import numpy as np\n",
"\n",
" # cell placement for 'portrait'.\n",
" if numcells == 72:\n",
" stdpl=np.array([[0,\t23,\t24,\t47,\t48,\t71],\n",
" [1,\t22,\t25,\t46,\t49,\t70],\n",
" [2,\t21,\t26,\t45,\t50,\t69],\n",
" [3,\t20,\t27,\t44,\t51,\t68],\n",
" [4,\t19,\t28,\t43,\t52,\t67],\n",
" [5,\t18,\t29,\t42,\t53,\t66],\n",
" [6,\t17,\t30,\t41,\t54,\t65],\n",
" [7,\t16,\t31,\t40,\t55,\t64],\n",
" [8,\t15,\t32,\t39,\t56,\t63],\n",
" [9,\t14,\t33,\t38,\t57,\t62],\n",
" [10,\t13,\t34,\t37,\t58,\t61],\n",
" [11,\t12,\t35,\t36,\t59,\t60]])\n",
" \n",
" elif numcells == 96:\n",
" stdpl=np.array([[0,\t23,\t24,\t47,\t48,\t71,\t72,\t95],\n",
" [1,\t22,\t25,\t46,\t49,\t70,\t73,\t94],\n",
" [2,\t21,\t26,\t45,\t50,\t69,\t74,\t93],\n",
" [3,\t20,\t27,\t44,\t51,\t68,\t75,\t92],\n",
" [4,\t19,\t28,\t43,\t52,\t67,\t76,\t91],\n",
" [5,\t18,\t29,\t42,\t53,\t66,\t77,\t90],\n",
" [6,\t17,\t30,\t41,\t54,\t65,\t78,\t89],\n",
" [7,\t16,\t31,\t40,\t55,\t64,\t79,\t88],\n",
" [8,\t15,\t32,\t39,\t56,\t63,\t80,\t87],\n",
" [9,\t14,\t33,\t38,\t57,\t62,\t81,\t86],\n",
" [10,\t13,\t34,\t37,\t58,\t61,\t82,\t85],\n",
" [11,\t12,\t35,\t36,\t59,\t60,\t83,\t84]])\n",
" else:\n",
" print(\"Error. Only 72 and 96 cells modules supported at the moment. Change numcells to either of this options!\")\n",
" return\n",
" \n",
" if portraitorlandscape == 'landscape':\n",
" stdpl = stdpl.transpose()\n",
" elif portraitorlandscape != 'portrait':\n",
" print(\"Error. portraitorlandscape variable must either be 'landscape' or 'portrait'\")\n",
" return\n",
" \n",
" cellsx = len(stdpl[1]); cellsy = len(stdpl)\n",
" \n",
" return stdpl, cellsx, cellsy"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"